package edu.uci.ics.ai.maze.views;

import edu.uci.ics.ai.maze.controllers.Game;
import edu.uci.ics.ai.maze.controllers.SearchEngine;
import edu.uci.ics.ai.maze.models.SearchAlgorithm;
import edu.uci.ics.ai.maze.utils.SystemUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.HeadlessException;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.JToolBar.Separator;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.UIManager;

/**
 * A basic JFC based application.
 */
public class GameFrame extends JFrame implements Observer {

  private static final Logger logger = Logger.getLogger(GameFrame.class.getName());
  private final Game game;
  private final MazeCanvas view;
  private final StatsDialog statsDialog;
  private final VisibleVertexDialog visVertexDialog;
  private final JMenu searchMenu = new JMenu();
  private final JMenuItem cancelSearchItem = new JMenuItem();
  private final JMenuItem showStatsDialogMenuItem = new JMenuItem();
  private final JMenuItem showVisibleVertexMenuItem = new JMenuItem();
  // Used by addNotify
  private boolean frameSizeAdjusted = false;

  public GameFrame(final Game game) {
    this.game = game;

    JMenuBar menuBar = new JMenuBar();
    setJMenuBar(menuBar);
    setTitle("Game");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    getContentPane().setLayout(new BorderLayout(0, 0));
    getContentPane().setBackground(new Color(204, 204, 204));

    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (Exception ex) {
      logger.log(Level.SEVERE, "Cannot load SystemLookAndFeel [error={0}]",
          ex.getLocalizedMessage());
    }
    setDefaultLookAndFeelDecorated(true);
    setSize(1000, 690);
    setVisible(false);

    AboutActionListener aboutListener = new AboutActionListener();
    NewFileActionListener newFileListener = new NewFileActionListener();
    OpenFileActionListener openFileListener = new OpenFileActionListener();
    CloseWindowListener closeWindowListener = new CloseWindowListener();

    ImageIcon newIcon = new ImageIcon("res/images/crystal/filenew.png");
    ImageIcon openIcon = new ImageIcon("res/images/crystal/fileopen.png");
    ImageIcon aboutIcon = new ImageIcon("res/images/crystal/help.png");

    initFileMenu(menuBar, newIcon, openIcon, newFileListener, openFileListener,
        closeWindowListener);
    initSearchMenu(menuBar);
    initViewMenu(menuBar);
    initDebugMenu(menuBar);
    initHelpMenu(menuBar, aboutIcon, aboutListener);
    initToolBar(newIcon, newFileListener,
        openIcon, openFileListener,
        aboutIcon, aboutListener);

    this.addWindowListener(closeWindowListener);


    visVertexDialog = new VisibleVertexDialog(this, game.getDialogModel(),
        showVisibleVertexMenuItem, searchMenu);
    visVertexDialog.setModal(false);
    visVertexDialog.setLocationRelativeTo(this);

    statsDialog = new StatsDialog(this, game.getModel(), showStatsDialogMenuItem);
    statsDialog.setModal(false);
    statsDialog.setLocationRelativeTo(this);

    view = new MazeCanvas(game.getModel(), game.getDialogModel());

    game.getSearchEngine().addObserver(this);
    getContentPane().add(BorderLayout.CENTER, view);
  }

  private JToolBar initToolBar(ImageIcon newIcon, NewFileActionListener newFileListener,
      ImageIcon openIcon, OpenFileActionListener openFileListener,
      ImageIcon aboutIcon, AboutActionListener aboutListener) {
    JToolBar toolPanel = new JToolBar();
    toolPanel.setAlignmentY(0.222222F);
    toolPanel.setBounds(0, 0, 97, 29);

    JButton newButton = new JButton();
    newButton.setToolTipText("Create a new document");
    newButton.setMnemonic((int) 'N');
    newButton.setBounds(16, 4, 23, 23);
    newButton.setIcon(newIcon);
    newButton.addActionListener(newFileListener);
    toolPanel.add(newButton);

    JButton openButton = new JButton();
    openButton.setToolTipText("Open an existing document");
    openButton.setMnemonic((int) 'O');
    openButton.setBounds(39, 4, 23, 23);
    openButton.setIcon(openIcon);
    openButton.addActionListener(openFileListener);
    toolPanel.add(openButton);

    toolPanel.add(new Separator());

    JButton aboutButton = new JButton();
    aboutButton.setToolTipText("Display program information, version number and copyright");
    aboutButton.setMnemonic((int) 'A');
    aboutButton.setBounds(72, 4, 23, 23);
    aboutButton.setIcon(aboutIcon);
    aboutButton.addActionListener(aboutListener);
    toolPanel.add(aboutButton);

    getContentPane().add(BorderLayout.NORTH, toolPanel);
    return toolPanel;
  }

  private void initFileMenu(JMenuBar menuBar, ImageIcon newIcon, ImageIcon openIcon,
      NewFileActionListener newFileListener, OpenFileActionListener openFileListener,
      CloseWindowListener closeWindowListener)
      throws HeadlessException {
    JMenu fileMenu = new JMenu("File");
    fileMenu.setActionCommand("File");
    fileMenu.setMnemonic((int) 'F');

    JMenuItem newItem = new JMenuItem("New");
    newItem.setHorizontalTextPosition(SwingConstants.RIGHT);
    newItem.setActionCommand("New");
    newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
    newItem.setMnemonic((int) 'N');
    newItem.setIcon(newIcon);
    newItem.addActionListener(newFileListener);

    fileMenu.add(newItem);

    JMenuItem openItem = new JMenuItem("Open...");
    openItem.setHorizontalTextPosition(SwingConstants.RIGHT);
    openItem.setActionCommand("Open...");
    openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
        Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
    openItem.setMnemonic((int) 'O');
    openItem.setIcon(openIcon);
    openItem.addActionListener(openFileListener);

    fileMenu.add(openItem);

    if (!SystemUtils.IS_MAC_OS_X) {
      fileMenu.add(new Separator());

      JMenuItem exitItem = new JMenuItem("Exit");
      exitItem.setActionCommand("Exit");
      exitItem.setMnemonic((int) 'X');
      exitItem.addActionListener(closeWindowListener);
      fileMenu.add(exitItem);
    }

    menuBar.add(fileMenu);
  }

  private JMenu initSearchMenu(JMenuBar menuBar) throws HeadlessException {
    searchMenu.setText("Search");
    searchMenu.setActionCommand("Search");
    searchMenu.setMnemonic((int) 'S');
    menuBar.add(searchMenu);
    displayLoadedSearchAlgorithms(searchMenu);
    searchMenu.add(new Separator());
    cancelSearchItem.setEnabled(false);
    cancelSearchItem.setText("Cancel Running Search");
    cancelSearchItem.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        GameFrame.this.game.getSearchEngine().stopSearch();
      }
    });

    searchMenu.add(cancelSearchItem);
    searchMenu.setEnabled(false);

    return searchMenu;
  }

  private void displayLoadedSearchAlgorithms(JMenu searchMenu) throws HeadlessException {
    JMenuItem searchItem;
    List<SearchAlgorithm> algos = game.getSearchEngine().getAlgorithms();
    if (algos.isEmpty()) {
      String s = "No Search Algorithms registered";
      searchItem = new JMenuItem(s);
      //searchItem.setActionCommand(s);
      searchItem.setEnabled(false);
      searchMenu.add(searchItem);
    } else {
      int mnemonic = 0;
      for (SearchAlgorithm search : game.getSearchEngine().getAlgorithms()) {
        mnemonic++;
        searchItem = new JMenuItem(search.getName());
        searchItem.setActionCommand(search.getName());
        searchItem.addActionListener(new SelectAlgorithmListener(search));
        searchItem.setAccelerator(
            KeyStroke.getKeyStroke(mnemonic,
            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
        searchItem.setMnemonic(mnemonic);
        searchMenu.add(searchItem);
      }
    }
  }

  private void initViewMenu(JMenuBar menuBar) {
    JMenu viewMenu = new JMenu();
    viewMenu.setText("View");
    viewMenu.setActionCommand("View");
    viewMenu.setMnemonic((int) 'V');
    menuBar.add(viewMenu);

    showStatsDialogMenuItem.setText("Show Statistic Window");
    showStatsDialogMenuItem.setActionCommand("Show Statistic Window");
    showStatsDialogMenuItem.setMnemonic((int) 'W');
    showStatsDialogMenuItem.addActionListener(new ShowStatsDialogListener());

    viewMenu.add(showStatsDialogMenuItem);
  }

  private JMenu initDebugMenu(JMenuBar menuBar) {
    JMenu debugMenu = new JMenu();
    debugMenu.setText("Debug");
    debugMenu.setActionCommand("Debug");
    debugMenu.setMnemonic((int) 'D');
    menuBar.add(debugMenu);

    showVisibleVertexMenuItem.setText("Visible Vertices Window");
    showVisibleVertexMenuItem.setActionCommand("Visible Vertices...");
    showVisibleVertexMenuItem.setMnemonic((int) 'V');
    showVisibleVertexMenuItem.setEnabled(false);
    showVisibleVertexMenuItem.addActionListener(new ShowVisibleVertexDialog());
    debugMenu.add(showVisibleVertexMenuItem);
    return debugMenu;
  }

  private JMenu initHelpMenu(JMenuBar menuBar, ImageIcon aboutIcon, ActionListener aboutListener) {
    JMenu helpMenu = new JMenu();
    helpMenu.setText("Help");
    helpMenu.setActionCommand("Help");
    helpMenu.setMnemonic((int) 'H');
    menuBar.add(helpMenu);

    JMenuItem aboutItem = new JMenuItem();
    aboutItem.setHorizontalTextPosition(SwingConstants.RIGHT);
    aboutItem.setText("About Game...");
    aboutItem.setActionCommand("About...");
    aboutItem.setMnemonic((int) 'A');
    aboutItem.setIcon(aboutIcon);
    aboutItem.addActionListener(aboutListener);

    helpMenu.add(aboutItem);
    return helpMenu;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (arg == SearchEngine.Observations.SEARCH_STARTED) {
      cancelSearchItem.setEnabled(true);

    } else if (arg == SearchEngine.Observations.SEARCH_ENDED) {
      cancelSearchItem.setEnabled(false);
    }
  }

  /**
   * Notifies this component that it has been added to a container
   * This method should be called by <code>Container.add</code>, and
   * not by user code directly.
   * Overridden here to adjust the size of the frame if needed.
   */
  @Override
  public void addNotify() {
    // Record the size of the window prior to calling parents addNotify.
    Dimension size = getSize();

    super.addNotify();

    if (frameSizeAdjusted) {
      return;
    }
    frameSizeAdjusted = true;

    // Adjust size of frame according to the insets and menu bar
    JMenuBar menuBar = getRootPane().getJMenuBar();
    int menuBarHeight = 0;
    if (menuBar != null) {
      menuBarHeight = menuBar.getPreferredSize().height;
    }
    Insets insets = getInsets();
    setSize(insets.left + insets.right + size.width,
        insets.top + insets.bottom + size.height + menuBarHeight);
  }

  private class NewFileActionListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent event) {
      setTitle("Game");
      game.getModel().clear();
      game.getDialogModel().resetState();
      if (visVertexDialog.isVisible()) {
        visVertexDialog.setVisible(false);
      }

      showVisibleVertexMenuItem.setEnabled(false);

      statsDialog.setSearchTime(0);
      statsDialog.setPathCost(0.0);

      if (statsDialog.isVisible()) {
        statsDialog.repaint();
      }

      searchMenu.setEnabled(false);
    }
  }

  private class OpenFileActionListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent event) {
      Cursor oldCursor = getCursor();
      FileDialog openFileDialog = new FileDialog(GameFrame.this);
      openFileDialog.setMode(FileDialog.LOAD);
      openFileDialog.setTitle("Open");

      try {
        openFileDialog.setVisible(true);

        String dirName = openFileDialog.getDirectory();
        String fileName = openFileDialog.getFile();
        String newTitle = "Game - " + fileName;

        if (fileName != null) {
          setCursor(new Cursor(Cursor.WAIT_CURSOR));
          game.loadFile(dirName + fileName);
          setTitle(newTitle);
          if (!visVertexDialog.isVisible()) {
            searchMenu.setEnabled(true);
            showVisibleVertexMenuItem.setEnabled(true);
          }
          statsDialog.setSearchTime(0);
          statsDialog.setPathCost(0.0);
          if (statsDialog.isVisible()) {
            statsDialog.repaint();
          }
          game.getDialogModel().resetState();
          if (visVertexDialog.isVisible()) {
            game.getDialogModel().calcVisibleInfo(game.getModel().getMaze().getStart());
          }

        }
      } catch (FileNotFoundException e) {
        JOptionPane.showMessageDialog(null, "File Not Found", "Error",
            JOptionPane.WARNING_MESSAGE);
      } catch (IOException e) {
        JOptionPane.showMessageDialog(null, e.getLocalizedMessage(), "Error",
            JOptionPane.WARNING_MESSAGE);
      } finally {
        setCursor(oldCursor);
      }
    }
  }

  private class CloseWindowListener extends WindowAdapter implements ActionListener {

    @Override
    public void windowClosing(WindowEvent event) {
      game.close();
    }

    @Override
    public void actionPerformed(ActionEvent event) {
      game.close();
    }
  }

  private class SelectAlgorithmListener implements ActionListener {

    private SearchAlgorithm search;

    private SelectAlgorithmListener(SearchAlgorithm search) {
      this.search = search;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
      Cursor oldCursor = getCursor();
      setCursor(new Cursor(Cursor.WAIT_CURSOR));
      game.getSearchEngine().search(search);
      setCursor(oldCursor);
      showStatsDialogMenuItem.setEnabled(false);
    }
  }

  private class ShowStatsDialogListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent event) {
      statsDialog.setVisible(true);
    }
  }

  private class ShowVisibleVertexDialog implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent event) {
      visVertexDialog.setVisible(true);
      game.getModel().resetPath();
      game.getModel().notifyObservers();
    }
  }

  private class AboutActionListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent event) {
      AboutDialog aboutDialog = new AboutDialog(GameFrame.this);
      aboutDialog.setModal(true);
      aboutDialog.setLocationRelativeTo(GameFrame.this);
      aboutDialog.setVisible(true);
    }
  }
}
